/**
 * yeganaaa@163.com
 * author: (مۇختەرجان مەخمۇت) muhtaer mahmood
 * 10/13/2019, 1:40:26 PM GMT+06:00
 */


import * as Http from "http"
import * as Fs from "fs"
import * as Mime from "mime"
import * as Url from "url"
import * as Path from "path"

export class StaticServer {
    protected nextServer: StaticServer = null!

    public constructor(public rootDir: string, public mappingPath: string = "/", public mimeTypeDetector: (fileName: string) => string = fileName => Mime.getType(fileName) as string) {

    }

    public handleCorrespondFilePath(request: Http.IncomingMessage, response: Http.ServerResponse): Promise<string> {
        let requestPath = Url.parse(request.url as string).pathname as string

        return new Promise<string>(async (resolve, reject) => {
            if (!requestPath.startsWith(this.mappingPath)) {
                if (this.nextServer == null) {
                    resolve(null!)
                    return
                }
                resolve(await this.nextServer.handleCorrespondFilePath(request, response))
                return
            }

            requestPath = requestPath.replace(this.mappingPath, "")
            let filePath = Path.join(this.rootDir, requestPath)
            filePath = Path.resolve(filePath)
            if (!filePath.startsWith(Path.resolve(this.rootDir))) {
                resolve(null!)
                return
            }
            Fs.exists(filePath, exists => {
                if (!exists) {
                    resolve(null!)
                    return
                }
                Fs.stat(filePath, (error, stat) => {
                    if (error) reject(error)
                    if (!stat.isFile()) {
                        resolve(null!)
                        return
                    }
                    resolve(filePath)
                    return
                })
            })
        })
    }

    public handle(request: Http.IncomingMessage, response: Http.ServerResponse): Promise<void> {
        return new Promise<void>(async (resolve, reject) => {
            try {
                const file = await this.handleCorrespondFilePath(request, response) as string
                if (file == null) {
                    reject(new Error("File not found in requested path, did you forget using 'canBeHandle' method?"))
                    return
                }
                // response.setHeader("Encoding", "UTF-8")
                response.setHeader("Content-Type", this.mimeTypeDetector(file))
                response.setHeader("Server", "Dream server")
                response.setHeader("Date", new Date().toDateString())
                Fs.stat(file, (error, stat) => {
                    if (error != null) {
                        reject(error)
                        return
                    }
                    response.setHeader("Content-Length", stat.size)
                    const inputStream = Fs.createReadStream(file)
                    inputStream.readableLength
                    inputStream.on("close", () => resolve())
                    inputStream.pipe(response)
                })
            } catch (error) {
                reject(error)
            }
        })
    }

    public canBeHandle(request: Http.IncomingMessage, response: Http.ServerResponse): Promise<boolean> {
        return new Promise<boolean>(async (resolve, reject) => {
            try {
                resolve(await this.handleCorrespondFilePath(request, response) != null)
            } catch (error) {
                reject(error)
            }
        })
    }

    public join(nextServer: StaticServer): StaticServer {
        function recursive(server: StaticServer) {
            if (server.nextServer == null) {
                server.nextServer = nextServer
                return
            }
            recursive(server.nextServer)
        }

        recursive(this)
        return this
    }
}
